import ast
import sys
import time
import math
import random
import logging
import datetime
import pandas as pd
import multiprocessing
from collections import OrderedDict
from deap import base, tools
from lbs_service import get_travel_time
from tools.lbs_utils import conn_pgsql

"""
将订单编号作为基因，手艺人作为种群
只做订单数量对等交换
1.优先选择路程时间不满足的订单
2.缓存交换方案，不做重复搜索
3.交叉概率动态调整，根据路程时间减小率成倍减小
通过交换固定位置的订单编号，搜索更优解
种群基因形式(首位为手艺人编号，-1代表不可用时间)：
[1, -1, 0, 157, 157, 157, 157, 157, 0, 412, 412, 412, 412, 412, 0, 640, 640, 640, 640, 640, 0, 908, 908, 908, 908, 0]
[53, 0, 0, 164, 164, 164, 164, 0, 0, 418, 418, 418, 418, 0, 0, 646, 646, 646, 646, 0, 0, 0, 0, 0, 0, -1]
抽取手艺人的开放时间，需要对关闭时间做检查和限制
（去掉关闭和不可用时间）
"""
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(lineno)d: %(message)s')
# 不起作用
# logging.getLogger("requests.packages.urllib3.connectionpool").setLevel(logging.WARNING)
logger = logging.getLogger(__name__)
searched_solutions = []
travel_time_error_orders = []
conn = None
df_artisans = None
df_orders = None
model_version = '1.0'


def create_conn():
    global conn
    if conn is None:
        conn = conn_pgsql()


create_conn()


def get_yesterday(diff_days=1):
    today = datetime.date.today()
    one_day = datetime.timedelta(days=diff_days)
    ret_day = today - one_day
    return ret_day.strftime("%Y-%m-%d")


def create_artisan():
    gene = df_artisans['genes'].values
    return gene


def get_artisan_index(se):
    artisan_index = se['genes'][0]
    return artisan_index


class IndividualList(list):
    """
    可增加属性的 List
    """
    def __new__(self, *args, **kwargs):
        return super(IndividualList, self).__new__(self, args, kwargs)

    def __init__(self, *args, **kwargs):
        if len(args) == 1 and hasattr(args[0], '__iter__'):
            list.__init__(self, args[0])
        else:
            list.__init__(self, args)
        self.__dict__.update(kwargs)

    def __call__(self, **kwargs):
        self.__dict__.update(kwargs)
        return self


def check_travel_time(conn, individual):
    """
    检查路程时间是否有效
    :param conn:
    :param individual:
    :return:
    """
    global df_artisans, df_orders, travel_time_error_orders
    error_routes = False
    artisan_orders = []
    artisan_id = individual[0]
    total_time = 0
    for order_id in individual[1:]:
        if order_id > 0:
            if len(artisan_orders) > 0:
                pre_order_id = artisan_orders[-1]
                pre_order = df_orders.iloc[pre_order_id - 1]
            else:
                # 如果是该手艺人第一单
                # logger.debug('artisan_id:' + str(artisan_id))
                pre_order = df_artisans.iloc[artisan_id]
            now_order = df_orders.iloc[order_id - 1]
            travel_time = 0
            if pre_order is not None:
                # 排除预约时间有冲突的情况
                try:
                    now_start = int(now_order['start'])
                except Exception as e:
                    logger.exception(e)
                    now_start = 0
                pre_time = pre_order['stop']
                if now_start < pre_time:
                    print('order[' + str(order_id) + '] service time ' + str(now_start) + ' < artisan[' + str(
                        artisan_id) + '] pre time ' + str(pre_time))
                    error_routes = True
                    break
                # 排除路程时间不够的情况
                try:
                    p1 = str(pre_order['lon']) + ',' + str(pre_order['lat'])
                    p2 = str(now_order['lon']) + ',' + str(now_order['lat'])
                    # 计算全天时间
                    start_hour = math.floor(now_order['start'] / (60 * 60) - 1)
                    if start_hour < 0:
                        start_hour = 0
                    start = str(start_hour) + ":00"
                    interval_time = int(now_order['start']) - int(pre_order['stop'])
                    travel_time = get_travel_time(conn, now_order['city_id'], p1, p2, start)
                    logger.debug('get travel_time: ' + str(travel_time))
                except Exception as e:
                    logger.exception(e)
                    travel_time = 0
                if travel_time > interval_time:
                    logger.debug(
                        'order[' + str(order_id) + '] interval time ' + str(interval_time) + ' < artisan[' + str(
                            artisan_id) + '] travel time ' + str(travel_time))
                    error_routes = True
                    break
            total_time += travel_time
    if error_routes:
        travel_time_error_orders.append(individual)
    return error_routes, total_time


def time_utilization(conn, individual):
    """
    评价个体的好坏，可观察该个体的时间利用率
    路程时间利用率 = 订单数量/路程时间
    交叉和突变时也要检查
    :param conn:
    :param individual:
    :return:
    """
    logger.debug('gene: ' + str(individual))
    # 检查时间窗口是否满足
    error_routes, total_time = check_travel_time(conn, individual)
    order_nums = len(set([x for x in individual[1:] if int(x) > 0]))
    if error_routes:
        # 不管路线是否有效，应返回订单数
        return 0, order_nums, total_time
    if total_time > 0:
        utilization = order_nums / total_time
    else:
        utilization = 0
    return utilization, order_nums, total_time


def eval_vrptw(individual):
    """
    进化过程评价
    :param individual: 个体对象
    :param instance: 个体相关数据
    :return:
    """
    global conn
    conn.dispose()
    usage, order_num, total_time = time_utilization(conn, individual)
    return [usage, order_num, total_time]


class IndividualFitness(base.Fitness):
    weights = (1,)

    def __init__(self, *args, **kwargs):
        base.Fitness.__init__(self, *args, **kwargs)


def create_individual(x):
    x = ast.literal_eval(x)
    fitness, order_num, travel_time = eval_vrptw(x)
    genes = IndividualList(x, fitness=IndividualFitness(values=(fitness,)), order_num=order_num, travel_time=travel_time)
    return genes


def create_artisans(n):
    global df_artisans
    genes = df_artisans['genes'].values[:n]
    return genes


def travel_time_worst(individuals, k):
    """
    优先选择路程时间有问题的订单
    选择订单数最差的情况
    :param: individual
    :param: k
    :return:
    """
    global travel_time_error_orders
    sel_error_num = min(len(travel_time_error_orders), k)
    error_inds = travel_time_error_orders[:sel_error_num]
    error_individuals = [i for i in individuals if i[0] in error_inds]
    sel_inds = tools.selWorst(individuals, max(k - sel_error_num, 0))
    sel_inds.extend(error_individuals)
    # 防止出现重复个体
    news_ids = []
    for id in sel_inds:
        if id not in news_ids:
            news_ids.append(id)
    travel_time_error_orders = []
    return sorted(news_ids)


def drop_duplicate(ind_list):
    distinct_list = []
    for i in ind_list:
        if i not in distinct_list:
            distinct_list.append(i)
    return distinct_list


def remove_dict_value(dic, v):
    new_dic = {k: d for k, d in dic.items() if d != v}
    return new_dic


def genes_to_align(ind1, ind2):
    """
    基因二次编码
    步骤：
     1.去掉同为0部分
     2.左遍历，尾对齐
     3.右遍历，首对齐
     4.去重并去除不合法项
    :param ind1:
    :param ind2:
    :return:
    """
    # 1.去掉同为0部分
    # [1, 1, 2, 0, 0, 3, 3, 7, 9, 0, 10, 10]
    # [4, 0, 5, 5, 6, 6, 0, 0, 8, 8, 8, 0]
    try:
        i1_align, i2_align = zip(*[(i1, i2) for i1, i2 in zip(ind1, ind2) if (i1 + i2) > 0])
        i1_align = list(i1_align)
        i2_align = list(i2_align)
    except Exception as e:
        logger.debug(e)
        i1_align = [0]
        i2_align = [0]
        return i1_align, i2_align
        # logger.debug(ind1)
        # logger.debug(ind2)
        # logger.exception(e)

    # 2.左遍历，尾对齐
    # [1,1,2,2,0,3,3,7,9,9,10,10]
    # [4,4,5,5,6,6,6,0,8,8,8,8]
    i1_pre_dna = 0
    i2_pre_dna = 0
    for i in range(len(i1_align)):
        if i1_align[i] == i1_pre_dna or i2_align[i] == i2_pre_dna:
            if i1_align[i] == 0:
                i1_align[i] = i1_pre_dna
            if i2_align[i] == 0:
                i2_align[i] = i2_pre_dna
        # print("i1_align[" + str(i) + "]=" + str(i1_align[i]) + " i1_pre_dna=" + str(i1_pre_dna))
        # print("i2_align[" + str(i) + "]=" + str(i2_align[i]) + " i2_pre_dna=" + str(i2_pre_dna))
        i1_pre_dna = i1_align[i]
        i2_pre_dna = i2_align[i]

    # 3.右遍历，首对齐
    # [1, 1, 2, 2, 3, 3, 3, 7, 9, 9, 10, 10]
    # [4, 4, 5, 5, 6, 6, 6, 0, 8, 8, 8, 8]
    i1_pre_dna = 0
    i2_pre_dna = 0
    for i in range(len(i1_align) - 1, -1, -1):
        if i1_align[i] == i1_pre_dna or i2_align[i] == i2_pre_dna:
            if i1_align[i] == 0:
                i1_align[i] = i1_pre_dna
            if i2_align[i] == 0:
                i2_align[i] = i2_pre_dna
        i1_pre_dna = i1_align[i]
        i2_pre_dna = i2_align[i]

    # 4.去重并去除不合法项
    check_dict = {}
    duplicate_keys = []
    duplicate_values = []
    check_set = OrderedDict.fromkeys((i1, i2) for i1, i2 in zip(i1_align, i2_align)).keys()
    for o in list(check_set):
        if o[0] * o[1] != 0:
            # print(o[0], o[1])
            # 如果键已存在，说明是1对多关系，删除
            if o[0] in check_dict.keys():
                duplicate_keys.append(o[0])
            # 如果值已存在，同上
            elif o[1] in check_dict.values():
                duplicate_values.append(o[1])
            check_dict[o[0]] = o[1]
    for d in duplicate_keys:
        try:
            del check_dict[d]
        except:
            pass
    for d in duplicate_values:
        check_dict = remove_dict_value(check_dict, d)
    return list(check_dict.keys()), list(check_dict.values())


def align_to_genes(aligned, no_work_index, order_index, order_hour):
    """
    二次编码解码
    :param aligned: [1, 2, 3]
    :param no_work_index: [0,1,2,3,24,25,45,46,47] 非工作时间
    :param order_index: {'1': 12, '2': 18, '3': 28}
    :param order_hour: {'1': 2, '2': 3, '3': 3}
    :return: [-1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 2, 2, 2, 0, 0, 0, -1, -1, 0, 0, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1]
    """
    gens = []
    order_id = 0
    repeat = 0
    # 开始组装
    for half_hour in range(24 * 2):
        if half_hour in no_work_index:
            code = -1
        elif repeat > 0:
            code = order_id
            repeat -= 1
        else:
            code = 0
            # 取出当前时间段的订单
            orders = [k for k, v in order_index.items() if v == half_hour]
            if orders is not None and len(orders) > 0:
                for a in aligned:
                    if str(a) in orders:
                        repeat = order_hour[str(a)] - 1
                        order_id = int(a)
                        code = order_id
                        break
        gens.append(code)
    if len(gens) != 48:
        logger.debug('gen genes length:' + str(len(gens)))
        logger.debug(gens)
    return gens


def get_no_work_index(ind):
    """
    取得不可交换的关闭时间
    :param ind: [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20, 20, 0, 0, -1, -1, -1]
    :return: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 45, 46, 47]
    """
    try:
        no_work_index = [i for i, v in enumerate(ind) if v == -1]
    except:
        no_work_index = []
    return no_work_index


def get_work_index(ind1, ind2):
    """
    取得可交换的开放时间
    :param ind1: [4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20, 20, 0, 0, -1, -1, -1]
    :param ind2: [6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 22, 22, 22, 0, 0, 0, 0, 0, 0, -1, -1]
    :return:
        [0, 0, 0, 0, 0, 0, 0, 0, 33, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20, 20, 0, 0]
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 22, 22, 22, 0, 0, 0, 0, 0]
    """
    try:
        no_work_index = [i for i, v in enumerate(ind1) if v == -1 or ind2[i] == -1]
    except:
        no_work_index = []
    work_ind1 = [order_id for i, order_id in enumerate(ind1) if i not in no_work_index]
    work_ind2 = [order_id for i, order_id in enumerate(ind2) if i not in no_work_index]
    return work_ind1, work_ind2


def get_order_index(work_ind):
    """
    取得订单位置
    :param work_ind: [0, 0, 0, 0, 0, 0, 0, 0, 33, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20, 20, 0, 0]
    :return: {'33': 8, '20': 20}
    """
    o_index = {}
    if work_ind is not None:
        for i, order_id in enumerate(work_ind):
            if order_id > 0 and str(order_id) not in o_index.keys():
                o_index[str(order_id)] = i
    return o_index


def get_order_hour(work_ind):
    """
    取得订单长度
    :param work_ind: [0, 0, 0, 0, 0, 0, 0, 0, 33, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20, 20, 0, 0]
    :return: {'33': 2, '20': 2}
    """
    o_hour = {}
    if work_ind is not None:
        o_hour = {str(order_id): work_ind.count(order_id) for i, order_id in enumerate(work_ind) if order_id > 0}
    return o_hour


def order_time_matched(ind1, ind2):
    """
    交叉方法
    手艺人订单基因中的订单时间必须匹配，才能交换变异
    0.首先排除两个个体不可用时间
    目前只考虑1种可交换的情况：
    1.个体时间对应：个体1与个体2中的订单占用时间可交换
    原始基因：
    [100,-1,1,0,2,0,0,0,3,3,0,7,0,9,0,10,10]
    [104,-1,0,0,5,5,0,6,6,0,0,0,0,8,8,8,0]
    二次编码
    可用于交换变异的基因别名：
    [2,3]
    [5,6]
    :param ind1: [4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20, 20, 0, 0, -1, -1, -1]
    :param ind2: [6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 22, 22, 22, 0, 0, 0, 0, 0, 0, -1, -1]
    :return:
    """
    # logger.debug('ind1: ' + str(len(ind1)) + ' ind2: ' + str(len(ind2)))
    # logger.debug(ind1)
    # logger.debug(ind2)
    genes1 = ind1[1:]
    genes2 = ind2[1:]
    if len(genes1) > 0 and len(genes2) > 0:
        no_work_index1 = get_no_work_index(genes1)
        no_work_index2 = get_no_work_index(genes2)
        work_ind1, work_ind2 = get_work_index(genes1, genes2)
        # 用字典记住订单的位置和长度，方便解码
        # logger.debug('work hours:')
        # logger.debug(work_ind1)
        # logger.debug(work_ind2)
        # 订单位置：order_index: {'1': 1, '2': 4, '3': 8}
        # 订单长度：order_hour: {'1': 2, '2': 1, '3': 2}
        # 记录不在交换工作区间内的订单
        order_index = get_order_index(genes1)
        no_order1 = list(order_index.keys())
        # logger.debug('order indexes:')
        # logger.debug(no_order1)
        order_index2 = get_order_index(genes2)
        no_order2 = list(order_index2.keys())
        # logger.debug(no_order2)
        order_index.update(order_index2)
        order_hour = get_order_hour(genes1)
        order_hour.update(get_order_hour(genes2))
        i1_aligned, i2_aligned = genes_to_align(work_ind1, work_ind2)
        if len(i1_aligned) >= 1 and len(i1_aligned) == len(i2_aligned):
            no_order1 = [int(o) for o in no_order1 if int(o) not in i1_aligned]
            no_order2 = [int(o) for o in no_order2 if int(o) not in i2_aligned]
            # logger.debug('no orders:')
            # logger.debug(no_order1)
            # logger.debug(no_order2)
            # logger.debug('old genes:')
            # logger.debug(i1_aligned)
            # logger.debug(i2_aligned)
            # 使用基因别名实现 均匀交叉（不会改变DNA位置）
            ind_prob = 1 / len(i1_aligned)
            i1, i2 = tools.cxUniform(i1_aligned, i2_aligned, ind_prob)
            i1.extend(no_order1)
            i2.extend(no_order2)
            # logger.debug('genes mated:')
            # logger.debug(i1)
            # logger.debug(i2)
            # logger.debug('no_work_index1: ' + str(no_work_index1))
            # logger.debug('no_work_index2: ' + str(no_work_index2))
            # logger.debug('order_index: ' + str(order_index))
            # logger.debug('order_hour: ' + str(order_hour))
            ind1[1:] = align_to_genes(i1, no_work_index1, order_index, order_hour)
            ind2[1:] = align_to_genes(i2, no_work_index2, order_index, order_hour)
        # logger.debug('new gens:')
        # logger.debug(ind1)
        # logger.debug(ind2)
    return ind1, ind2


def no_mutate(individual):
    return individual


def ga_vrptw(pool_size, pop_size, cx_pb, gen_size, better_limit=15, file_name='_0_0'):
    """
    遗传算法主程序
    :param pool_size: 线程池数量
    :param pop_size: 种群数量
    :param cx_pb: 交叉概率
    :param gen_size: 最多进化次数
    :param better_limit: 最多派单次数
    :param file_name: 文件后缀
    :return:
    """
    global searched_solutions
    toolbox = base.Toolbox()
    if pool_size <= 0:
        pool = multiprocessing.Pool()
    else:
        pool = multiprocessing.Pool(pool_size)
    toolbox.register("map", pool.map)
    # 定义种群生成器
    # 不需要随机生成N个种群，只需要在现有固定接单手艺人的基础上交叉变异即可
    toolbox.register('population', create_artisans)
    # 生成种群
    pop = toolbox.population(n=pop_size)
    # 定义最小交叉概率(确保算法具备微调的能力)
    min_cx_pb = 1 / pop_size
    # 定义评价方法
    toolbox.register('evaluate', eval_vrptw)
    # 定义选择方法 每次选择最差配种（优先选择路程时间有问题的）
    # toolbox.register('select', tools.selWorst)
    toolbox.register('select', travel_time_worst)
    # 定义交叉方法
    toolbox.register('mate', order_time_matched)
    # 定义突变方法
    # 订单时间位置特殊，突变会导致时间窗口被破坏，不进行突变
    toolbox.register('mutate', no_mutate)
    # Begin the evolution
    start_time = time.time()
    g_total = 0     # 定义总迭代次数
    g_found = 0     # 定义优化次数
    g_one = 0       # 定义每轮迭代次数
    fitnesses = list(toolbox.map(toolbox.evaluate, pop))
    for ind, fit in zip(pop, fitnesses):
        ind.fitness.values = fit[0],
        ind.order_num = fit[1]
        ind.travel_time = fit[2]
    fits = [ind.fitness.values[0] for ind in pop]
    best_fit = sum(fits)
    best_fits = [best_fit]
    order_nums = sum([ind.order_num for ind in pop])
    logger.debug('start optimize: artisans[%i], orders[%i]' % (len(pop), order_nums))
    total_order_nums = [order_nums]
    total_travel_time = sum([ind.travel_time for ind in pop])
    total_travel_times = [total_travel_time]
    generations = [g_one]
    end_time = time.time()
    run_time = end_time - start_time
    run_times = [run_time]
    total_g_run_time = 0
    searched_solutions = [sorted(pop)]
    best_solution = []
    # 第一代种群中 未找到有效派单方案
    if len(fits) == 0:
        return "no feasible solution found, please increase population size."
    start_time = time.time()
    # 限制总迭代次数、优化次数、总迭代时间
    while g_total <= gen_size and g_found <= better_limit and total_g_run_time <= 60 * 60:
        # A new generation
        g_start_time = time.time()
        g_total += 1
        g_one += 1
        logger.debug("-- Generation %i -- Total Generation %i -- Found %i --" % (g_one, g_total, g_found))
        # Select the next generation individuals
        offspring = toolbox.select(pop, len(pop))
        # 保留未选择的父代个体
        pre_pop = [x for x in pop if x not in offspring]
        # Clone the selected individuals
        offspring = list(map(toolbox.clone, offspring))
        # logger.debug("select " + str(len(offspring)) + " individuals")
        # logger.debug("remain " + str(len(pre_pop)) + " individuals")
        # Apply crossover and mutation on the offspring
        for child1, child2 in zip(offspring[::2], offspring[1::2]):
            # cross two individuals with probability CXPB
            if random.random() < cx_pb:
                toolbox.mate(child1, child2)
                del child1.fitness.values
                del child2.fitness.values
        new_solution = list(offspring)
        new_solution.extend(pre_pop)
        new_solution = sorted(new_solution)
        total_order_num = 0
        total_travel_time = 0
        fits = [0]
        if new_solution not in searched_solutions:
            searched_solutions.append(new_solution)
            # Evaluate the individuals with an invalid fitness
            good_solution = True
            invalid_ind = [ind for ind in offspring if not ind.fitness.valid]
            fitnesses = map(toolbox.evaluate, invalid_ind)
            for ind, fit in zip(invalid_ind, fitnesses):
                # 路程时间有问题时 fit=0
                if fit[0] == 0:
                    good_solution = False
                    logger.debug('error solution')
                    break
                ind.fitness.values = fit[0],
                ind.order_num = fit[1]
                ind.travel_time = fit[2]
            if good_solution:
                logger.debug(" Evaluated %i individuals" % len(invalid_ind))
                # The population is entirely replaced by the offspring
                pop = list(offspring)
                pop.extend(pre_pop)
                # Gather all the fitnesses in one list and print the stats
                fits = [ind.fitness.values[0] for ind in pop]
                total_order_num = sum([ind.order_num for ind in pop])
                total_travel_time = sum([ind.travel_time for ind in pop])
        # 对丢单进行惩罚，不允许出现丢单的现象
        if total_order_num < order_nums:
            now_fit = 0
        else:
            now_fit = sum(fits)
            logger.debug('Find a feasible solution, fitness: ' + str(now_fit))
            logger.debug('artisans[%i], orders[%i]' % (len(pop), total_order_num))
        if (best_fit == 0 and g_found == 0) or best_fit < now_fit:
            # 如果 fit 增速减缓，则缩小交换尺度
            if total_travel_times[-1] > 0:
                rate = (total_travel_times[-1] - total_travel_time) / total_travel_times[-1]
            else:
                rate = 0
            if g_total > pop_size and rate <= 0.02 and cx_pb/2 > min_cx_pb:
                cx_pb = cx_pb / 2
                logger.debug('rate: ' + str(rate) + ', reduce cx_pb: ' + str(cx_pb))
            if cx_pb < min_cx_pb:
                cx_pb = min_cx_pb
            best_fit = now_fit
            best_fits.append(best_fit)
            total_order_nums.append(total_order_num)
            total_travel_times.append(total_travel_time)
            generations.append(g_one)
            best_solution = pop
            logger.debug("Find a better solution, fitness: %s , travel_time: %d" % (str(now_fit), total_travel_time))
            end_time = time.time()
            run_time = end_time - start_time
            run_times.append(run_time)
            start_time = time.time()
            g_found += 1
            g_one = 0
        g_end_time = time.time()
        total_g_run_time += (g_end_time - g_start_time)
    pool.close()
    logger.debug("Total Generation %i / %i -- Found %i / %i " % (g_total, gen_size, g_found, better_limit))
    logger.debug("-- End of (successful) evolution --")
    # 此处不同于传统的进化算法挑选个体
    # 而是挑选进化过程中得分最高的某一代
    if best_fit > 0:
        logger.debug("Best population is: ")
        logger.debug(best_solution)
        logger.debug(best_fit)
        best_df = pd.DataFrame({'genes': best_solution})
        if len(best_df) > 0:
            best_df['artisan_id'] = best_df.apply(get_artisan_index, axis=1)
            best_df.sort_values('artisan_id', inplace=True)
            best_df.to_csv('/data/disp/' + yesterday + '/result' + file_name + '.csv')
        eval_df = pd.DataFrame({'fits': best_fits, 'order_num': total_order_nums, 'travel_time': total_travel_times,
                                'generation': generations, 'run_time': run_times})
        eval_df.to_csv('/data/disp/' + yesterday + '/eval' + file_name + '.csv')


def train(gen_size=100, better_limit=15, city_id='0', cate_id='0'):
    """
    进化主程序
    :param gen_size:
    :param better_limit:
    :param city_id:
    :param cate_id:
    :return:
    """
    global df_artisans, model_version
    df_artisans['genes'] = df_artisans['genes'].apply(create_individual)
    pop_size = len(df_artisans)
    pool_size = math.ceil(pop_size/2)
    file_tail_name = '_' + city_id + '_' + cate_id + '_' + model_version
    # 交叉概率(意味着手艺人50%的订单会发生交叉)
    cx_pb = 0.5
    # Global creation of the individuals for GA
    ga_vrptw(
        pool_size=pool_size,
        pop_size=pop_size,
        cx_pb=cx_pb,
        gen_size=gen_size,
        better_limit=better_limit,
        file_name=file_tail_name
    )


if __name__ == '__main__':
    # 北京、上海、杭州、深圳
    # 'tag_mei_rong', 'tag_mei_jia', 'tag_mei_jie', 'tag_wei_zheng'
    try:
        city_id = int(sys.argv[1]) - 1
    except:
        city_id = 0
    try:
        cate_id = int(sys.argv[2]) - 1
    except:
        cate_id = 0
    try:
        day_interval = int(sys.argv[3])
    except:
        day_interval = 1
    if city_id < 0:
        city_id = 0
    if cate_id < 0:
        cate_id = 0
    if day_interval < 1:
        day_interval = 1
    try:
        yesterday = get_yesterday(day_interval)
        df_artisans = pd.read_csv('/data/disp/' + yesterday + '/artisans_' + str(city_id) + '_' + str(cate_id) + '.csv')
        df_orders = pd.read_csv('/data/disp/' + yesterday + '/orders_' + str(city_id) + '_' + str(cate_id) + '.csv')
    except:
        df_artisans = None
        logger.debug('no data file, stop')
    if df_artisans is not None:
        tic = time.process_time()
        train(30, 15, str(city_id), str(cate_id))
        logger.debug('Computing Time: %s' % (time.process_time() - tic))
